package com.tfswx.jcyw.print.log;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.tfswx.jcyw.print.config.Ggpz;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Marker;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * @author 张述江
 * @date 2023/12/18
 */
public class Logger implements org.slf4j.Logger {
    private final String name;

    private static final boolean debug;

    static {
        ClassPathResource resource = new ClassPathResource("sample.fxml");
        debug = !resource.getUrl().toString().startsWith("jar:");
    }

    public Logger(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        if (StringUtils.isBlank(name)) {
            return "default";
        }
        return name;
    }

    @Override
    public boolean isTraceEnabled() {
        return false;
    }

    @Override
    public void trace(String msg) {
        appendLog(getLogEvent(LogLevel.TRACE, msg, null, null));
    }

    @Override
    public void trace(String format, Object arg) {
        appendLog(getLogEvent(LogLevel.TRACE, format, new Object[]{arg}, null));
    }

    @Override
    public void trace(String format, Object arg1, Object arg2) {
        appendLog(getLogEvent(LogLevel.TRACE, format, new Object[]{arg1, arg2}, null));
    }

    @Override
    public void trace(String format, Object... arguments) {
        appendLog(getLogEvent(LogLevel.TRACE, format, arguments, null));
    }

    @Override
    public void trace(String msg, Throwable t) {
        appendLog(getLogEvent(LogLevel.TRACE, msg, null, t));
    }

    @Override
    public boolean isTraceEnabled(Marker marker) {
        return false;
    }

    @Override
    public void trace(Marker marker, String msg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void trace(Marker marker, String format, Object arg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void trace(Marker marker, String format, Object arg1, Object arg2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void trace(Marker marker, String format, Object... argArray) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void trace(Marker marker, String msg, Throwable t) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isDebugEnabled() {
        return false;
    }

    @Override
    public void debug(String msg) {
        appendLog(getLogEvent(LogLevel.DEBUG, msg, null, null));
    }

    @Override
    public void debug(String format, Object arg) {
        appendLog(getLogEvent(LogLevel.DEBUG, format, new Object[]{arg}, null));
    }

    @Override
    public void debug(String format, Object arg1, Object arg2) {
        appendLog(getLogEvent(LogLevel.DEBUG, format, new Object[]{arg1, arg2}, null));
    }

    @Override
    public void debug(String format, Object... arguments) {
        appendLog(getLogEvent(LogLevel.DEBUG, format, arguments, null));
    }

    @Override
    public void debug(String msg, Throwable t) {
        appendLog(getLogEvent(LogLevel.DEBUG, msg, null, t));
    }

    @Override
    public boolean isDebugEnabled(Marker marker) {
        return false;
    }

    @Override
    public void debug(Marker marker, String msg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void debug(Marker marker, String format, Object arg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void debug(Marker marker, String format, Object arg1, Object arg2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void debug(Marker marker, String format, Object... arguments) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void debug(Marker marker, String msg, Throwable t) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isInfoEnabled() {
        return false;
    }

    @Override
    public void info(String msg) {
        appendLog(getLogEvent(LogLevel.INFO, msg, null, null));
    }

    @Override
    public void info(String format, Object arg) {
        appendLog(getLogEvent(LogLevel.INFO, format, new Object[]{arg}, null));

    }

    @Override
    public void info(String format, Object arg1, Object arg2) {
        appendLog(getLogEvent(LogLevel.INFO, format, new Object[]{arg1, arg2}, null));

    }

    @Override
    public void info(String format, Object... arguments) {
        appendLog(getLogEvent(LogLevel.INFO, format, arguments, null));

    }

    @Override
    public void info(String msg, Throwable t) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isInfoEnabled(Marker marker) {
        return false;
    }

    @Override
    public void info(Marker marker, String msg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void info(Marker marker, String format, Object arg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void info(Marker marker, String format, Object arg1, Object arg2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void info(Marker marker, String format, Object... arguments) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void info(Marker marker, String msg, Throwable t) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isWarnEnabled() {
        return false;
    }

    @Override
    public void warn(String msg) {
        appendLog(getLogEvent(LogLevel.WARN, msg, null, null));
    }

    @Override
    public void warn(String format, Object arg) {
        appendLog(getLogEvent(LogLevel.WARN, format, new Object[]{arg}, null));
    }

    @Override
    public void warn(String format, Object... arguments) {
        appendLog(getLogEvent(LogLevel.WARN, format, arguments, null));
    }

    @Override
    public void warn(String format, Object arg1, Object arg2) {
        appendLog(getLogEvent(LogLevel.WARN, format, new Object[]{arg1, arg2}, null));
    }

    @Override
    public void warn(String msg, Throwable t) {
        appendLog(getLogEvent(LogLevel.WARN, msg, null, t));
    }

    @Override
    public boolean isWarnEnabled(Marker marker) {
        return false;
    }

    @Override
    public void warn(Marker marker, String msg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void warn(Marker marker, String format, Object arg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void warn(Marker marker, String format, Object arg1, Object arg2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void warn(Marker marker, String format, Object... arguments) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void warn(Marker marker, String msg, Throwable t) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isErrorEnabled() {
        return false;
    }

    @Override
    public void error(String msg) {
        appendLog(getLogEvent(LogLevel.ERROR, msg, null, null));
    }

    @Override
    public void error(String format, Object arg) {
        appendLog(getLogEvent(LogLevel.ERROR, format, new Object[]{arg}, null));
    }

    @Override
    public void error(String format, Object arg1, Object arg2) {
        appendLog(getLogEvent(LogLevel.ERROR, format, new Object[]{arg1, arg2}, null));
    }

    @Override
    public void error(String format, Object... arguments) {
        appendLog(getLogEvent(LogLevel.ERROR, format, arguments, null));
    }

    @Override
    public void error(String msg, Throwable t) {
        appendLog(getLogEvent(LogLevel.ERROR, msg, null, t));
    }

    @Override
    public boolean isErrorEnabled(Marker marker) {
        return false;
    }

    @Override
    public void error(Marker marker, String msg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void error(Marker marker, String format, Object arg) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void error(Marker marker, String format, Object arg1, Object arg2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void error(Marker marker, String format, Object... arguments) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void error(Marker marker, String msg, Throwable t) {
        throw new UnsupportedOperationException();
    }

    private LogObj getLogEvent(LogLevel level, String message, Object[] args, Throwable t) {
        Throwable throwable = new Throwable();
        StackTraceElement[] stackTrace = throwable.getStackTrace();
        StackTraceElement stackTraceElement = stackTrace[2];
        LogObj logObj = new LogObj();
        logObj.setDate(new Date());
        logObj.setClassName(stackTraceElement.getClassName());
        logObj.setMethodName(stackTraceElement.getMethodName());
        logObj.setLineNumber(stackTraceElement.getLineNumber());
        logObj.setThreadName(Thread.currentThread().getName());
        logObj.setLogLevel(level.name());
        logObj.setMessage(message);
        logObj.setParams(args);
        logObj.setThrowable(t);

        return logObj;
    }

    private String getLogDate() {
        LocalDateTime now = LocalDateTime.now();
        return now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
    }

    private String getLogDate(String pattern) {
        LocalDateTime now = LocalDateTime.now();
        return now.format(DateTimeFormatter.ofPattern(pattern));
    }

    private void appendLog(LogObj log) {
        RingBuffer<LogEvent> ringBuffer = disruptor.getRingBuffer();
        long next = ringBuffer.next();
        LogEvent logEvent = ringBuffer.get(next);
        BeanUtil.copyProperties(log, logEvent);
        ringBuffer.publish(next);
    }

    private static final Disruptor<LogEvent> disruptor = getDisruptor();

    public synchronized static Disruptor<LogEvent> getDisruptor() {
        if (disruptor != null) {
            return disruptor;
        }
        // 定义用于事件处理的线程池， Disruptor通过java.util.concurrent.ExecutorSerivce提供的线程池来触发consumer的事件处理。
        ThreadFactory executor = Executors.defaultThreadFactory();

        EventFactory<LogEvent> factory = LogEvent::new;
        int ringBufferSize = 1024 * 1024;
        // 单线程模式，获取额外的性能
        Disruptor<LogEvent> disruptor = new Disruptor<>(factory, ringBufferSize, executor, ProducerType.SINGLE, new BlockingWaitStrategy());
        WorkHandler<LogEvent> workHandler = o -> {
            File logFile = new File(Ggpz.LOG_DIR, o.getLogLevel() + "/" + o.getLogLevel() + "-" + DateUtil.format(new Date(), "yyyy-MM-dd") + ".log");
            logFile.getParentFile().mkdirs();

            String msg = DateUtil.format(o.getDate(), DatePattern.NORM_DATETIME_MS_PATTERN) + " " +
                    StringUtils.rightPad(o.getLogLevel(), 5) +
                    " [" + o.getThreadName() + "] " +
                    o.getClassName() + ":" + o.getLineNumber() + " " +
                    o.getFormattedMessage();


            try (PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(logFile, true), StandardCharsets.UTF_8))) {
                printWriter.println(msg);
                if (debug) {
                    System.out.println(msg);
                }
                if (o.getThrowable() != null) {
                    printWriter.println(ExceptionUtils.getStackTrace(o.getThrowable()));
                    if (debug) {
                        o.getThrowable().printStackTrace();
                    }
                }

                printWriter.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        };

        disruptor.handleEventsWithWorkerPool(workHandler);
        disruptor.setDefaultExceptionHandler(new ExceptionHandler<LogEvent>() {
            @Override
            public void handleEventException(Throwable throwable, long l, LogEvent logEvent) {
                System.out.println("handleEventException");
                throwable.printStackTrace();
            }

            @Override
            public void handleOnStartException(Throwable throwable) {
                System.out.println("handleOnStartException");
                throwable.printStackTrace();
            }

            @Override
            public void handleOnShutdownException(Throwable throwable) {
                System.out.println("handleOnShutdownException");
                throwable.printStackTrace();
            }
        });
        disruptor.start();
        return disruptor;
    }

}
